/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unused-vars */
import qs from 'qs';
import { notNilEmpty } from 'qx-util';
import { AxiosError } from 'axios';
import { UniResponse } from './uni-response';
import { INet } from '../../interface';
import { IHttpResponse } from './http-response';
import { HttpError, HttpErrorFactory, RuntimeError } from '../../error';

/**
 * UniApp 请求
 *
 * @export
 * @class UniNet
 * @implements {INet}
 */
export class UniNet implements INet {
  /**
   * 请求队列
   *
   * @protected
   * @memberof UniNet
   */
  protected waitRequest = new Map<string, Promise<IHttpResponse>>();

  /**
   * Creates an instance of UniNet.
   * @param {IData} [defaultConfig]
   * @memberof UniNet
   */
  constructor(private defaultConfig?: IData) {}

  /**
   * 获取基础路径
   *
   * @readonly
   * @protected
   * @type {string}
   * @memberof UniNet
   */
  protected get baseUrl(): string {
    return (
      this.defaultConfig?.baseURL || `${ibiz.env.baseUrl}/${ibiz.env.appId}`
    );
  }

  /**
   * 获取请求配置
   *
   * @protected
   * @param {string} url
   * @param {IParams} config
   * @return {*}  {UniApp.RequestOptions}
   * @memberof UniNet
   */
  protected getRequestConfig(
    url: string,
    config: IParams,
  ): UniApp.RequestOptions {
    return {
      ...config,
      dataType: 'json',
      url: `${this.baseUrl}${url}`,
      method: config.method?.toUpperCase(),
      responseType: ibiz.env.mobPlatform !== 'mp-weixin' ? 'json' : undefined,
    };
  }

  /**
   * 基础请求方法
   *
   * @param {string} url
   * @param {IParams} config
   * @return {*}  {Promise<IHttpResponse>}
   * @memberof UniNet
   */
  async request(url: string, config: IParams): Promise<IHttpResponse> {
    const cfg = this.getRequestConfig(url, config);
    // 当前请求的唯一标识
    const key = JSON.stringify(cfg);
    try {
      let requestPromise: Promise<IHttpResponse> | undefined;
      if (!this.waitRequest.has(key)) {
        requestPromise = new Promise<IHttpResponse>((resolve, reject) => {
          uni.request({
            ...cfg,
            success(res) {
              if (res.statusCode === 200) {
                resolve(new UniResponse(res));
              } else {
                const message: string = res.data as string;
                reject(
                  new HttpError({
                    message,
                    response: {
                      data: message,
                      status: res.statusCode,
                      statusText: '',
                      config: { headers: res.header },
                      headers: res.header,
                    },
                  }),
                );
              }
            },
          });
        });
        this.waitRequest.set(key, requestPromise);
      } else {
        requestPromise = this.waitRequest.get(key);
      }
      const response = await requestPromise;
      // 当第一个请求完成后就删除等待队列中的请求
      if (this.waitRequest.has(key)) {
        this.waitRequest.delete(key);
      }
      return response!;
    } catch (error) {
      // 请求异常删除等待队列中的请求体
      if (this.waitRequest.has(key)) {
        this.waitRequest.delete(key);
      }
      throw HttpErrorFactory.getInstance(error as AxiosError);
    }
  }

  /**
   * Post 请求
   *
   * @param {string} url
   * @param {IData} data
   * @param {IParams} [params={}]
   * @param {IData} [header={}]
   * @return {*}  {Promise<IHttpResponse>}
   * @memberof UniNet
   */
  async post(
    url: string,
    data: IData,
    params: IParams = {},
    header: IData = {},
  ): Promise<IHttpResponse> {
    url = this.handleAppPresetParam(url, params, data);
    try {
      const response = await this.request(url, {
        method: 'post',
        data,
        header,
      });
      return response;
    } catch (error) {
      throw HttpErrorFactory.getInstance(error as AxiosError);
    }
  }

  /**
   * Get 请求
   *
   * @param {string} url
   * @param {IParams} [params={}]
   * @param {IData} [header={}]
   * @param {IParams} [config={}]
   * @return {*}  {Promise<IHttpResponse>}
   * @memberof UniNet
   */
  async get(
    url: string,
    params: IParams = {},
    header: IData = {},
    config: IParams = {},
  ): Promise<IHttpResponse> {
    url = this.attachUrlParam(url, params);
    try {
      const response = await this.request(url, {
        header,
        method: 'get',
        ...config,
      });
      return response;
    } catch (error) {
      throw HttpErrorFactory.getInstance(error as AxiosError);
    }
  }

  /**
   * Delete 请求
   *
   * @param {string} url
   * @param {IParams} [params={}]
   * @param {IData} [header={}]
   * @return {*}  {Promise<IHttpResponse>}
   * @memberof UniNet
   */
  async delete(
    url: string,
    params: IParams = {},
    header: IData = {},
  ): Promise<IHttpResponse> {
    url = this.handleAppPresetParam(url, params);
    try {
      const response = await this.request(url, { method: 'delete', header });
      return response;
    } catch (error) {
      throw HttpErrorFactory.getInstance(error as AxiosError);
    }
  }

  /**
   * Put 请求
   *
   * @param {string} url
   * @param {IData} data
   * @param {IParams} [params={}]
   * @param {IData} [header={}]
   * @return {*}  {Promise<IHttpResponse>}
   * @memberof UniNet
   */
  async put(
    url: string,
    data: IData,
    params: IParams = {},
    header: IData = {},
  ): Promise<IHttpResponse> {
    url = this.handleAppPresetParam(url, params);
    try {
      const response = await this.request(url, { method: 'put', data, header });
      return response;
    } catch (error) {
      throw HttpErrorFactory.getInstance(error as AxiosError);
    }
  }

  /**
   * 触发 sse 请求
   *
   * @param {string} url
   * @param {IParams} params
   * @param {IParams} [options={}]
   * @return {*}  {Promise<void>}
   * @memberof UniNet
   */
  async sse(
    url: string,
    params: IParams,
    options: IParams = {},
  ): Promise<void> {
    throw new RuntimeError(ibiz.i18n.t('core.utils.unrealized'));
  }

  /**
   * axios 请求
   *
   * @param {IParams} config
   * @return {*}  {Promise<void>}
   * @memberof UniNet
   */
  axios(config: IParams): Promise<void> {
    throw new RuntimeError(ibiz.i18n.t('core.utils.unrealized'));
  }

  /**
   * 获取模型数据
   *
   * @param {string} url
   * @param {IData} [header={}]
   * @return {*}  {Promise<IHttpResponse>}
   * @memberof UniNet
   */
  async getModel(url: string, header: IData = {}): Promise<IHttpResponse> {
    try {
      const response = await this.request(url, {
        header,
      });
      return response;
    } catch (error) {
      throw HttpErrorFactory.getInstance(error as AxiosError);
    }
  }

  /**
   * 处理平台预定义参数
   *
   * @private
   * @param {string} url
   * @param {IParams} params
   * @param {IData} [data={}]
   * @return {*}  {string}
   * @memberof UniNet
   */
  private handleAppPresetParam(
    url: string,
    params: IParams,
    data: IData = {},
  ): string {
    // [特殊参数识别]post请求时将srfversionid视图参数或编辑器中的视图参数以请求问号参数传递
    if (data && Object.prototype.hasOwnProperty.call(data, 'srfversionid')) {
      params.srfversionid = data.srfversionid;
    }
    if (params) {
      return this.attachUrlParam(url, params);
    }
    return url;
  }

  /**
   * url 附加请求参数，并处理路径的字符转换 encode
   *
   * @private
   * @param {string} url
   * @param {IParams} params
   * @return {*}  {string}
   * @memberof UniNet
   */
  private attachUrlParam(url: string, params: IParams): string {
    {
      // url 转码
      const urlSplit = url.split('?');
      urlSplit[0] = urlSplit[0]
        .split('/')
        .map(item => encodeURIComponent(item))
        .join('/');
      url = urlSplit.length > 1 ? urlSplit.join('?') : urlSplit[0];
    }
    const strParams: string = qs.stringify(params);
    if (notNilEmpty(strParams)) {
      if (url.endsWith('?')) {
        url = `${url}${strParams}`;
      } else if (url.indexOf('?') !== -1 && url.endsWith('&')) {
        url = `${url}${strParams}`;
      } else if (url.indexOf('?') !== -1 && !url.endsWith('&')) {
        url = `${url}&${strParams}`;
      } else {
        url = `${url}?${strParams}`;
      }
    }
    return url;
  }
}
